// Copyright 2007 Google Inc.
// All Rights Reserved.

package com.google.jessewilson.codesearch.gtagssearch;

import com.google.inject.Inject;
import com.google.jessewilson.codesearch.api.CodeFile;
import com.google.jessewilson.codesearch.api.Line;
import com.google.jessewilson.codesearch.api.Page;
import com.google.jessewilson.codesearch.api.Query;
import com.google.jessewilson.codesearch.api.SearchProvider;
import com.google.jessewilson.codesearch.api.SearchProviderException;
import com.google.jessewilson.codesearch.api.SearchResult;
import com.google.jessewilson.codesearch.api.spi.SearchProviderBackend;
import com.google.jessewilson.codesearch.io.IO;
import com.google.jessewilson.codesearch.util.ProducerSink;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Search provider implementation for Gtags
 * 
 * @author Ted Mao (tmao@google.com)
 */
class GtagsSearchProviderBackend implements SearchProviderBackend {

  private final GtagsConfiguration configuration;
  private final Logger logger = Logger.getLogger(this.getClass().getName());

  @Inject
  public GtagsSearchProviderBackend(GtagsConfiguration configuration) {
    this.configuration = configuration;
  }

  public SearchResult search(Query query, ProducerSink<Line> sink, Page page)
      throws SearchProviderException {

    int totalResults = 0;
    BufferedReader reader = null;
    GtagsResultParser parser = new GtagsResultParser(this);

    try {
      Process process = startProcess(query);
      reader = new BufferedReader(new InputStreamReader(
          process.getInputStream()));
      for (String resultLine = reader.readLine(); resultLine != null;
           resultLine = reader.readLine()) {
        boolean success = parseAndAddLine(parser, resultLine, sink);
        if (success) {
          totalResults++;
        }
      }
    } catch (IOException e) {
      throw new SearchProviderException(e);
    } finally {
      IO.close(reader);
    }

    SearchResult result = new SearchResult();
    result.setTotalResults(totalResults);
    result.setHasMoreResults(false);
    return result;
  }

  private Process startProcess(Query query) throws IOException {
    GtagsLanguageType languageType = GtagsLanguageType.ofFile(
        query.getEditorFile());
    GtagsSearchProviderParameters parameters =
        (GtagsSearchProviderParameters) query.getSearchProviderParameters();
    List<String> command = GtagsCommand.buildCommand(
        configuration.getExecutable(), query.getSearchTerm(), languageType,
        parameters.findInvocations());
    return new ProcessBuilder(command).redirectErrorStream(true).start();
  }

  private boolean parseAndAddLine(GtagsResultParser parser, String resultLine,
      ProducerSink<Line> sink) {
    try {
      sink.produced(parser.parse(resultLine));
      return true;
    } catch (IllegalArgumentException e) {
      // perhaps the output format changed?  FIXME.
      logger.log(Level.WARNING, "Could not parse result line", e);
      return false;
    }
  }

  public void downloadFile(CodeFile source, File target)
      throws SearchProviderException {
    try {
      InputStream in = new FileInputStream(source.getPath().toString());
      OutputStream out = new FileOutputStream(target);
      IO.pumpStreams(in, out);
    } catch (IOException e) {
      throw new SearchProviderException(e);
    }
  }

  public SearchProvider getSearchProvider() {
    return new GtagsSearchProvider();
  }
}
